1 module cgi.cgi; 2 /***************************************************************************** 3 * package: cgi 4 * module cgi.cgi 5 * File: cgi.d 6 * Description: utility d module that contains Classes to build cgi applications. 7 * Author: Joseph M. Rice (ricejm01@gmail.com) 8 * Date: Wed Dec 18 00:04:03 EST 2014 9 * 10 *MIT License 11 * 12 *Copyright (c) 2014-2016 Joseph M. Rice <ricejm01@gmail.com> 13 * 14 *Permission is hereby granted, free of charge, to any person obtaining a copy 15 *of this software and associated documentation files (the "Software"), to deal 16 *in the Software without restriction, including without limitation the rights 17 *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 *copies of the Software, and to permit persons to whom the Software is 19 *furnished to do so, subject to the following conditions: 20 * 21 *The above copyright notice and this permission notice shall be included in all 22 *copies or substantial portions of the Software. 23 * 24 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 *SOFTWARE. 31 ***************************************************************************/ 32 33 import std.stdio; 34 import std..string; 35 import std.conv; 36 import std.process; 37 import std.algorithm; 38 import std.uri; 39 import std.exception; 40 41 42 class CGIException : Exception 43 { 44 this(string msg, string file = __FILE__, size_t line = __LINE__) { 45 super(msg, file, line); 46 } 47 } 48 49 /** 50 * this enumeration allows us to describe the type 51 * of content the cgi class will be returning and 52 * allows us to programatically control the behavior 53 * of how the what the application is writing to stdout. 54 * I.E. if content is generally text/html generate the html headers 55 * or if we are a text/plain (possibly a .csv or .txt) file 56 * behave this way. 57 */ 58 enum MIMETYPE { 59 TEXT_HTML=0, 60 TEXT_HTML_NO_CACHE, 61 TEXT_HTML_RAW, 62 TEXT_HTML_NO_CACHE_RAW, 63 TEXT_PLAIN, 64 TEXT_PLAIN_NO_CACHE, 65 RAW_TYPE 66 } 67 68 /** 69 * Abritrary modes that cgi variable can have 70 * 71 * Normal mode is that the variable is not sticky 72 * Forward mode is that upon the next form submission or 73 * post/get the cgi varibles should be sent along to be 74 * "sticky" to keep state. 75 */ 76 enum CGIMODES { 77 NORMAL=0, 78 FORWARD=1 79 } 80 81 /** 82 * Abritrary modes or states that a cgi variable could be 83 * 84 * Normal mode is the varibale is data 85 * File mode is to indicate the varible is a file. 86 */ 87 enum CGI_VAR_TYPE { 88 NORMAL=0, 89 FILE=1 90 } 91 92 /** 93 * CGIFILE class is a data structure that describes the way a 94 * file is reprensented from the web server. (apache) 95 * 96 * Note: currently a glorifed Struct, a class with no methods 97 * as that future plans are to expand this class. 98 */ 99 class CGIFILE { 100 string filename; 101 string content_type; 102 string content; 103 104 this() { 105 } 106 } 107 108 /** 109 * CGIVALS class is a data structure that represents the value 110 * key pairs that a cgi query string represents. IE. 111 * ("?foo=bar&foobar=be") where this query string has two CGIVALs 112 * contained within. this class allows us to store variables and 113 * logically describe what those variables are and assign a mode. 114 */ 115 class CGIVALS { 116 string name; 117 string value; 118 CGIMODES mode; 119 CGI_VAR_TYPE type; 120 CGIFILE fData; 121 122 this() { 123 name = ""; 124 value = ""; 125 mode = CGIMODES.NORMAL; 126 type = CGI_VAR_TYPE.NORMAL; 127 fData = new CGIFILE(); 128 } 129 } 130 131 /** 132 * see it's a COOKIE class and it's good enough for me. 133 * 134 * COOKIE class is a data structure that represents the components 135 * of an html cookie. 136 */ 137 class COOKIE { 138 string data; 139 string expires; 140 string path; 141 string domain; 142 143 this() { 144 this.data = ""; 145 this.expires = ""; 146 this.path = ""; 147 this.domain = ""; 148 } 149 150 this(string http_cookie) { 151 parseCookieString(http_cookie); 152 } 153 154 /** 155 * parse the cookie string and break into key=value pairs 156 * 157 *Note: this function is simplistic and assumes ("data payload"; expires= ; path= ; domain= ;) for the cookie string. 158 * so when you use cookies you have to have them in this format. To do: is make this method smarter. 159 */ 160 void parseCookieString(string http_cookie) { 161 if (http_cookie.length > 0) { 162 auto token = findSplit(http_cookie,"; expires="); 163 164 if (token[1] == "; expires=") { 165 this.data = chompPrefix(chomp(strip(token[0].idup),"\""),"\""); 166 token = findSplit(token[2],"; path="); 167 } 168 169 if (token[1] == "; path=") { 170 this.expires = strip(token[0].idup); 171 token = findSplit(token[2],"; domain="); 172 } 173 174 if (token[1] == "; domain=") { 175 this.path = strip(token[0].idup); 176 this.domain = strip(token[2].idup); 177 } 178 } else { 179 this.data = ""; 180 this.expires = ""; 181 this.path = ""; 182 this.domain = ""; 183 } 184 } 185 186 /** 187 * return a string reprenstation in html of the cookie 188 */ 189 override string toString() { 190 return data~"; expires="~expires~"; path="~path~";";// domain="~domain~";"; 191 } 192 } 193 194 195 /** 196 * CGI class 197 * 198 * Designed to read the environment variables created by web server and 199 * will consume the data and make it easily accessible to program with. 200 */ 201 class CGI { 202 // 203 // Class Varibles 204 // 205 CGIVALS[] cgiVals; 206 string HTTP_USER_AGENT; 207 string REQUEST_METHOD; 208 string PATH_INFO; 209 string CONTENT_TYPE; 210 string CONTENT_LENGTH; 211 string QUERY_STRING; 212 string SCRIPT_NAME; 213 string SERVER_NAME; 214 string BASE_URL; 215 string HTTP_COOKIE; 216 string SERVER_PORT; 217 string HTTP_HOST; 218 string REQUEST_URI; 219 string HTTPS; 220 string REMOTE_ADDR; 221 private string custom_content_type; 222 char[] postData; 223 private MIMETYPE mime_type; 224 private int mime_type_set; 225 private int CGI_ARRAY_START_SIZE=25; 226 private string error_msg; 227 private COOKIE cookie; 228 229 // 230 // Constructor(s) 231 // 232 this() { 233 mime_type_set = 0; 234 this.init(); 235 } 236 237 this(MIMETYPE type) { 238 mime_type = type; 239 mime_type_set=1; 240 this.init(); 241 } 242 243 ~this() { 244 this.destroyCgiData(); 245 } 246 // 247 // Private Methods 248 // 249 private void init() { 250 int ret = -9; 251 // 252 //Read environment varibles. 253 // 254 REQUEST_METHOD = environment.get("REQUEST_METHOD"); 255 HTTP_USER_AGENT = environment.get("HTTP_USER_AGENT"); 256 PATH_INFO = environment.get("PATH_INFO"); 257 CONTENT_TYPE = environment.get("CONTENT_TYPE"); 258 CONTENT_LENGTH = environment.get("CONTENT_LENGTH"); 259 QUERY_STRING = environment.get("QUERY_STRING"); 260 SCRIPT_NAME = environment.get("SCRIPT_NAME"); 261 SERVER_NAME = environment.get("SERVER_NAME"); 262 SERVER_PORT = environment.get("SERVER_PORT"); 263 HTTP_COOKIE = environment.get("HTTP_COOKIE"); 264 HTTP_HOST = environment.get("HTTP_HOST"); 265 REQUEST_URI = environment.get("REQUEST_URI"); 266 HTTPS = environment.get("HTTPS"); 267 REMOTE_ADDR = environment.get("HTTP_X_REAL_IP"); // Nginx proxy forwarding 268 if (REMOTE_ADDR.length <= 0) { 269 REMOTE_ADDR = environment.get("REMOTE_ADDR"); 270 } 271 272 cookie = new COOKIE(); 273 274 if (HTTPS != "on") { 275 BASE_URL = "http://"~ SERVER_NAME ~ SCRIPT_NAME; 276 } else { 277 BASE_URL = "https://"~ SERVER_NAME ~ SCRIPT_NAME; 278 } 279 280 if (REQUEST_METHOD == "POST" || 281 REQUEST_METHOD == "PUT") { 282 if (CONTENT_LENGTH.length == 0) { 283 throw new CGIException("ERROR: CONTENT_LENGTH environment variable is not present!"); 284 } 285 286 int buff_size = to!int(CONTENT_LENGTH); 287 288 if (buff_size != 0) { 289 // set our buffer size. 290 postData = new char[buff_size]; 291 // Read it all at once :) 292 stdin.rawRead(postData); 293 } else { 294 // there is nothing to read so just set an empty buffer 295 postData = new char[0]; 296 297 } 298 299 if (CONTENT_TYPE == "application/x-www-form-urlencoded") 300 QUERY_STRING ~= postData.idup; 301 } 302 303 // the foling are just handleded because it assumed that 304 // we already grabbed the needed environment variables 305 // and we will be leaving it up to the application developer 306 // to check method type if they care about it. 307 //REQUEST_METHOD == "DELETE" 308 //REQUEST_METHOD == "GET" 309 310 this.initCGIData(); 311 } 312 313 private int initMultipartCGIData(string postData) { 314 string boundary; 315 string haystack; 316 string data; 317 string content_disp; 318 string content_type; 319 string content; 320 string filename; 321 string name; 322 bool isFile = false; 323 int i = 0; 324 int ret = 1; 325 326 // 327 // set cgivals array start size 328 // 329 if (cgiVals.length == 0) { 330 cgiVals.length = CGI_ARRAY_START_SIZE; 331 } 332 333 // 334 //read bountry string from CONTENT_TYPE; 335 // 336 //if (CONTENT_TYPE != "multipart/form-data") { 337 if (!(startsWith(CONTENT_TYPE,"multipart/form-data;"))) { 338 ret = -2; 339 return ret; 340 } 341 342 auto boundary_split = findSplit(CONTENT_TYPE, "boundary="); 343 344 if (boundary_split[2].length <= 0) { 345 throw new CGIException("ERROR: Could not locate multipart/form-data boundry value!"); 346 } 347 348 // for some reason the CONTENT_TYPE boundary does not have the correct 349 // number of '-' So I'm adding 2 so everything lines up. 350 boundary = "--" ~ boundary_split[2].idup; 351 // 352 // skip the start boundary so we can logically findSplit 353 // 354 auto token = findSplit(postData,boundary); // skip the first boundry. 355 // 356 // Parse multipart/form-data 357 // 358 haystack = token[2].idup; 359 360 //we know to end when our haystack is "--\r\n" 361 for (i = 0; haystack.length > 0 && haystack != "--\r\n"; i++) { 362 token = findSplit(haystack,boundary); 363 // 364 // Grow our cgi array if we need too. 365 // 366 if (i == cgiVals.length) 367 cgiVals.length *= 2; 368 // 369 // The content-disposition 370 // 371 data = token[0].idup; 372 373 // ok, the boundary also has a "\r\n" at the end, except for the end it has "--\r\n" 374 // so our data now begins with "\r\n" so we have to account for this. 375 auto data_token = findSplit(data, "\r\n"); 376 data_token = findSplit(data_token[2], "\r\n"); 377 378 content_disp = data_token[0].idup; 379 // 380 // The content-type 381 // 382 data_token = findSplit(data_token[2], "\r\n"); 383 content_type = data_token[0].idup; 384 385 // 386 // The content 387 // 388 content = chomp(chompPrefix(data_token[2].idup,"\r\n"),"\r\n"); 389 // 390 //parse content_disposition 391 // 392 //Content-Disposition: form-data; name="file"; filename="package.json" 393 // 394 // Find filename 395 // 396 data_token = findSplit(content_disp,"filename="); 397 if (data_token[2].length) { 398 // we have a file name and we are a file 399 isFile = true; 400 filename = chomp(chompPrefix(data_token[2],"\""),"\""); 401 } 402 // 403 // Find name 404 // 405 data_token = findSplit(content_disp,"name="); 406 if (isFile) { 407 data_token = findSplit(data_token[2],";"); 408 name = chomp(chompPrefix(data_token[0],"\""),"\""); 409 } else { 410 name = chomp(chompPrefix(data_token[2],"\""),"\""); 411 } 412 413 // 414 // Store the parsed data 415 // 416 cgiVals[i] = new CGIVALS; 417 418 cgiVals[i].name = name.idup; //name 419 if(!isFile) { 420 cgiVals[i].value = content.idup; //value 421 } else { 422 cgiVals[i].value = ""; 423 cgiVals[i].type = CGI_VAR_TYPE.FILE; 424 cgiVals[i].fData.filename = filename.idup; 425 cgiVals[i].fData.content_type = content_type.idup; 426 cgiVals[i].fData.content = content.idup; 427 } 428 // 429 // Make the Haystack smaller 430 // 431 haystack = token[2].idup; 432 } 433 434 cgiVals.length = i; 435 ret = i; 436 437 return ret; 438 } 439 440 //look at QUERY_STRING and parse our cgivals. 441 private void initCGIData() { 442 string haystack = QUERY_STRING.idup; 443 int i = 0, ret = 0; 444 445 if (postData.length > 0 && // CONTENT_TYPE = multipart/form-data; boundary=------------------------cdcd8bbbb646b6ae 446 (startsWith(CONTENT_TYPE,"multipart/form-data;"))) { 447 //CONTENT_TYPE == "multipart/form-data") { 448 ret = initMultipartCGIData(to!string(postData)); 449 } 450 451 if (ret >= 0) { 452 if (cgiVals.length == 0) { 453 cgiVals.length = CGI_ARRAY_START_SIZE; 454 } else { 455 cgiVals.length *= 2; 456 } 457 458 for (i = ret; haystack.length > 0 ; i++) { 459 auto pair = findSplit(haystack,"&"); 460 auto vals = findSplit(pair[0],"="); 461 if (i == cgiVals.length) 462 cgiVals.length *= 2; 463 cgiVals[i] = new CGIVALS; 464 cgiVals[i].name = vals[0].idup; //name 465 cgiVals[i].value= urlDecode(vals[2]).idup; //value 466 haystack = pair[2].idup; 467 } 468 cgiVals.length = i; 469 } 470 } 471 472 473 private void destroyCgiData() { 474 int i = 0; 475 for (i = 0; i != cgiVals.length; ++i) { 476 destroy(cgiVals[i]); 477 } 478 } 479 480 private string urlDecode(string url) { 481 return decodeComponent(url); 482 } 483 484 private string urlEncode(string url) { 485 return encodeComponent(url); 486 } 487 488 // 489 // Class Methods 490 // 491 492 /** 493 * Check if a cgi variable exists with the passed in name. 494 * 495 * returns true if the variable exists. 496 */ 497 bool exists(string name) { 498 bool ret = false; 499 int i = 0; 500 501 for (i = 0; i != cgiVals.length; ++i) { 502 if (cgiVals[i] && cgiVals[i].name == name) { 503 ret = true; 504 break; 505 } 506 } 507 return ret; 508 } 509 510 /** 511 * Check if a cgi variable exists with the passed in name and index. 512 * keep in mind that the cgi environment can have multiple variables 513 * with the same name the passed in index allows us to specify which 514 * one to check for. 515 * 516 * returns true if the variable exists. 517 */ 518 bool exists(string name, int index) { 519 bool ret = false; 520 int i = 0, count = 0; 521 522 for (i = 0; i != cgiVals.length; ++i) { 523 if (cgiVals[i] && cgiVals[i].name == name) { 524 if (count == index) { 525 ret = true; 526 break; 527 } 528 count++; 529 } 530 } 531 return ret; 532 } 533 534 /** 535 * Get the value for the cgi variable for the passed in name 536 * 537 * return a string with the cgi variables value. 538 */ 539 string getVal(string name) { 540 string ret = ""; 541 int i = 0; 542 543 for (i = 0; i != cgiVals.length; ++i) { 544 if (cgiVals[i] && cgiVals[i].name == name) { 545 ret = (cgiVals[i].value).idup; 546 break; 547 } 548 } 549 550 return ret; 551 } 552 553 /** 554 * Get the value for the cgi variable for the passed in name and index. 555 * keep in mind that the cgi environment can have multiple variables 556 * of the same name the index allows us to specify which one we want. 557 * 558 * return a string with the cgi variables value. 559 */ 560 string getVal(string name, int index) { 561 string ret = ""; 562 int i = 0; 563 int count = 0; 564 565 for (i = 0; i != cgiVals.length; ++i) { 566 if (cgiVals[i] && cgiVals[i].name == name) { 567 if (count == index) { 568 ret = (cgiVals[i].value).idup; 569 break; 570 } 571 count++; 572 } 573 } 574 return ret; 575 } 576 577 /** 578 * Since the cgi environment can have multiple variables we might 579 * want to get all the values. 580 * 581 * return a string array of all the values for a varible name 582 */ 583 string[] getVal_m(string name) { 584 string[] ret; 585 int i = 0; 586 int count = 0; 587 588 if (ret.length == 0) { 589 ret.length = CGI_ARRAY_START_SIZE; 590 } 591 592 for (i = 0; i != cgiVals.length; ++i) { 593 594 if (count == ret.length) { 595 ret.length *= 2; 596 } 597 598 if (cgiVals[i] && cgiVals[i].name == name) { 599 ret[count] = (cgiVals[i].value).idup; 600 count++; 601 } 602 } 603 ret.length = count; 604 return ret; 605 } 606 607 /** 608 * Instead of reading the cgi data only from the environment, 609 * we can set our own cgi variables from memory. 610 */ 611 void setVal(string name, string value, CGIMODES mode, 612 CGI_VAR_TYPE type, CGIFILE fData) { 613 ulong i = cgiVals.length; 614 615 cgiVals.length += 1; 616 617 cgiVals[i] = new CGIVALS; 618 cgiVals[i].name = name; 619 cgiVals[i].value = value; 620 cgiVals[i].mode = mode; 621 cgiVals[i].type = type; 622 if (fData) { 623 cgiVals[i].fData.filename = fData.filename; 624 cgiVals[i].fData.content_type = fData.content_type; 625 cgiVals[i].fData.content = fData.content; 626 } 627 628 } 629 /** 630 * set the cgi Variable mode of a given name 631 */ 632 void setVarMode(string name, CGIMODES mode) { 633 int i = 0; 634 635 for (i = 0; i != cgiVals.length; ++i) { 636 if (cgiVals[i] && cgiVals[i].name == name) { 637 cgiVals[i].mode = mode; 638 break; 639 } 640 } 641 } 642 643 /** 644 * set the cgi Variable mode of a given name and index 645 * Keep in mind the cgi environment can contain multiple 646 * variables with the same name. The index allows us to 647 * get the one we want. 648 */ 649 void setVarMode(string name, CGIMODES mode, int index) { 650 int i = 0; 651 int count = 0; 652 653 for (i = 0; i != cgiVals.length; ++i) { 654 if (cgiVals[i] && cgiVals[i].name == name) { 655 if (count == index) { 656 cgiVals[i].mode = mode; 657 break; 658 } 659 count++; 660 } 661 } 662 } 663 664 /** 665 * Get the cgi Variable mode of a given name. 666 * 667 * returns the CGIMODES enum value for the variable with the name passed. 668 */ 669 CGIMODES getVarMode(string name) { 670 int i = 0; 671 CGIMODES ret; 672 673 for (i = 0; i != cgiVals.length; ++i) { 674 if (cgiVals[i] && cgiVals[i].name == name) { 675 ret = cgiVals[i].mode; 676 } 677 } 678 return ret; 679 } 680 681 /** 682 * Get the cgi Variable mode of a given name and index 683 * Keep in mind the cgi environment can contain multiple 684 * variables with the same name. The index allows us to 685 * get the one we want. 686 * 687 * returns the CGIMODES enum value for the variable with the name passed. 688 */ 689 CGIMODES getVarMode(string name, int index) { 690 int i = 0; 691 int count = 0; 692 CGIMODES ret; 693 694 for (i = 0; i != cgiVals.length; ++i) { 695 if (cgiVals[i] && cgiVals[i].name == name) { 696 if (count == index) { 697 ret = cgiVals[i].mode; 698 break; 699 } 700 count++; 701 } 702 } 703 return ret; 704 } 705 706 /** 707 * create a string of the cgi variables that are in forward mode. 708 * This allows us to use cgi variables to keep state and forward 709 * them on to another form submission. This allows the programmer 710 * to selectivly forward and unforward cgi variables for flow/state 711 * control. 712 * 713 * returns a string containing query string of the variables in forward mode. 714 */ 715 string getForwardVarString() { 716 string ret = ""; 717 int i = 0; 718 719 for (i = 0; i != cgiVals.length; ++i) { 720 if (cgiVals[i].mode == CGIMODES.FORWARD) { 721 ret ~= cgiVals[i].name ~ "=" ~ urlEncode(cgiVals[i].value); 722 if ((i + 1) != cgiVals.length) { 723 ret ~= "&"; 724 } 725 } 726 } 727 728 return ret; 729 } 730 731 /** 732 * Check to see if a cgi variable with name is a file or not. 733 * 734 * returns true if the cgi variable is file. 735 */ 736 bool varIsFile(string name) { 737 bool ret = false; 738 int i = 0; 739 740 for (i = 0; i != cgiVals.length; ++i) { 741 if (cgiVals[i] && 742 cgiVals[i].name == name && 743 cgiVals[i].type == CGI_VAR_TYPE.FILE) { 744 ret = true; 745 break; 746 } 747 } 748 749 return ret; 750 } 751 752 /** 753 * Check to see if a cgi variable with name and index is a file or not. 754 * keep in mind that the cgi environments may contain several variables 755 * with same name. the index allows us to specify which one. 756 * 757 * returns true if the cgi variable is file. 758 */ 759 bool varIsFile(string name,int index) { 760 bool ret = false; 761 int i = 0; 762 int count = 0; 763 764 for (i = 0; i != cgiVals.length; ++i) { 765 if (cgiVals[i] && 766 cgiVals[i].name == name && 767 cgiVals[i].type == CGI_VAR_TYPE.FILE) { 768 if (count == index) { 769 ret = true; 770 break; 771 } 772 count++; 773 } 774 } 775 776 return ret; 777 } 778 779 /** 780 * Get the file content type of the the file posted/put in the cgi envirnment 781 * with the variable string name. 782 * 783 * returns a string containing the original content type of the file uploaded. 784 */ 785 string getFileContentType(string name) { 786 string ret = ""; 787 int i = 0; 788 789 for (i = 0; i != cgiVals.length; ++i) { 790 if (cgiVals[i] && 791 cgiVals[i].name == name && 792 cgiVals[i].type == CGI_VAR_TYPE.FILE) { 793 ret = cgiVals[i].fData.content_type.idup; 794 break; 795 } 796 } 797 798 return ret; 799 } 800 801 /** 802 * Get the file content type of the the file posted/put in the cgi envirnment 803 * with the variable string name with the index. 804 * Keep in mind that a variable can exist multiple times in the cgi environment 805 * and the index allows us to specify exactly which one. 806 * 807 * returns a string containing the original content type of the file uploaded. 808 */ 809 string getFileContentType(string name, int index) { 810 string ret = ""; 811 int i = 0; 812 int count = 0; 813 814 for (i = 0; i != cgiVals.length; ++i) { 815 if (cgiVals[i] && 816 cgiVals[i].name == name && 817 cgiVals[i].type == CGI_VAR_TYPE.FILE) { 818 if (count == index) { 819 ret = cgiVals[i].fData.content_type.idup; 820 break; 821 } 822 count++; 823 } 824 } 825 826 return ret; 827 } 828 829 /** 830 * Get the file name of the the file posted/put in the cgi envirnment 831 * with the variable string name. 832 * 833 * returns a string containing the original name of the file uploaded. 834 */ 835 string getFileName(string name) { 836 string ret = ""; 837 int i = 0; 838 839 for (i = 0; i != cgiVals.length; ++i) { 840 if (cgiVals[i] && 841 cgiVals[i].name == name && 842 cgiVals[i].type == CGI_VAR_TYPE.FILE) { 843 ret = cgiVals[i].fData.filename.idup; 844 break; 845 } 846 } 847 848 return ret; 849 } 850 851 /** 852 * Get the file name of the the file posted/put in the cgi envirnment 853 * with the variable string name with the index. 854 * Keep in mind that a variable can exist multiple times in the cgi environment 855 * and the index allows us to specify exactly which one. 856 * 857 * returns a string containing the original name of the file uploaded. 858 */ 859 string getFileName(string name, int index) { 860 string ret = ""; 861 int i = 0; 862 int count = 0; 863 864 for (i = 0; i != cgiVals.length; ++i) { 865 if (cgiVals[i] && 866 cgiVals[i].name == name && 867 cgiVals[i].type == CGI_VAR_TYPE.FILE) { 868 if (count == index) { 869 ret = cgiVals[i].fData.filename.idup; 870 break; 871 } 872 count++; 873 } 874 } 875 876 return ret; 877 } 878 879 /** 880 * Get the content of a file in the cgi environment with string name. 881 * 882 * returns a string of the file's content. 883 */ 884 string getFileContent(string name) { 885 string ret = ""; 886 int i = 0; 887 888 for (i = 0; i != cgiVals.length; ++i) { 889 if (cgiVals[i] && 890 cgiVals[i].name == name && 891 cgiVals[i].type == CGI_VAR_TYPE.FILE) { 892 ret = cgiVals[i].fData.content.idup; 893 break; 894 } 895 } 896 897 return ret; 898 } 899 900 /** 901 * Get the content of a file in the cgi environment with string name and the index. 902 * keep in mind that we can have the same cgi variable with string name several times 903 * index allows us to specify which one we want. 904 * 905 * returns a string of the file's content. 906 */ 907 string getFileContent(string name, int index) { 908 string ret = ""; 909 int i = 0; 910 int count = 0; 911 912 for (i = 0; i != cgiVals.length; ++i) { 913 if (cgiVals[i] && 914 cgiVals[i].name == name && 915 cgiVals[i].type == CGI_VAR_TYPE.FILE) { 916 if (count == index) { 917 ret = cgiVals[i].fData.content.idup; 918 break; 919 } 920 count++; 921 } 922 } 923 924 return ret; 925 } 926 927 /** 928 * Get a file from the cgi enviroment of string name 929 * returns a CGIFILE class. 930 */ 931 CGIFILE getFile(string name) { 932 CGIFILE ret; 933 int i = 0; 934 935 for (i = 0; i != cgiVals.length; ++i) { 936 if (cgiVals[i] && 937 cgiVals[i].name == name && 938 cgiVals[i].type == CGI_VAR_TYPE.FILE) { 939 ret = cgiVals[i].fData; 940 break; 941 } 942 } 943 944 return ret; 945 } 946 947 948 /** 949 * 950 */ 951 CGIFILE getFile(string name, int index) { 952 CGIFILE ret; 953 int i = 0; 954 int count = 0; 955 956 for (i = 0; i != cgiVals.length; ++i) { 957 if (cgiVals[i] && 958 cgiVals[i].name == name && 959 cgiVals[i].type == CGI_VAR_TYPE.FILE) { 960 if (count == index) { 961 ret = cgiVals[i].fData; 962 break; 963 } 964 count++; 965 } 966 } 967 968 return ret; 969 } 970 971 /** 972 * Dumps the system environment variables to the page. 973 */ 974 void dumpEnv() { 975 auto env = environment.toAA(); 976 auto keys = env.keys; 977 auto values = env.values; 978 979 for (int i = 0; i != values.length; ++i) { 980 writefln("%s = %s<br>",keys[i],values[i]); 981 } 982 983 } 984 985 /** 986 * Dumps the contents of the CGI class to the page. Very useful for debuging 987 */ 988 void dump() { 989 int i = 0; 990 writeln("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=<br>"); 991 writeln("+ Environment =<br>"); 992 writeln("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=<br>"); 993 writefln("REQUEST_METHOD = %s<br>",this.REQUEST_METHOD); 994 writefln("HTTP_USER_AGENT = %s<br>",this.HTTP_USER_AGENT); 995 writefln("PATH_INFO = %s<br>",this.PATH_INFO); 996 writefln("CONTENT_TYPE = %s<br>",this.CONTENT_TYPE); 997 writefln("CONTENT_LENGTH = %s<br>",this.CONTENT_LENGTH); 998 writefln("QUERY_STRING = %s<br>",this.QUERY_STRING); 999 writefln("HTTP_COOKIE = %s<br>",this.HTTP_COOKIE); 1000 writefln("BASE_URL = %s<br>",this.BASE_URL); 1001 writefln("SCRIPT_NAME = %s<br>",this.SCRIPT_NAME); 1002 writeln("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=<br>"); 1003 writeln("+ CGI vals =<br>"); 1004 writeln("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=<br>"); 1005 for (i = 0; i != cgiVals.length; ++i) { 1006 if (cgiVals[i]) { 1007 if (cgiVals[i].type == CGI_VAR_TYPE.FILE) { 1008 writefln("Name = (%s) | filename = (%s) |" ~ 1009 " content-type (%s)| forward = (%d) <br>", 1010 cgiVals[i].name,cgiVals[i].fData.filename, 1011 cgiVals[i].fData.content_type,cgiVals[i].mode); 1012 } else { 1013 writefln("Name = (%s) | value = (%s) | forward = (%d)<br>", 1014 cgiVals[i].name,cgiVals[i].value,cgiVals[i].mode); 1015 } 1016 } 1017 } 1018 writeln("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=<br>"); 1019 1020 } 1021 1022 /** 1023 * Print to standard out http headers initialize the page. 1024 * 1025 * handles the standard http header of content type a carriage 1026 * return followed by two new lines, etc... for you. so that the 1027 * web server can return correct data back to the calling browser. 1028 * 1029 * depending on the MIMETYPE value we have different behaviors 1030 * 1031 * Type TEXT_HTML, will write out the http header, cookies, and start 1032 * the html for you if you want to be lazy and just concentrate whats 1033 * the <body> tag only. 1034 * 1035 * Type TEXT_HTML_NO_CACHE is basically the same behavior as TEXT_HTML, 1036 * but it sets the browsers cache behavior to not cache the page. 1037 * 1038 * Types TEXT_HTML_RAW and TEXT_HTML_NO_CACHE_RAW write out the HTTP header 1039 * information and cookie information only. It does not start the HTML for you! 1040 * you will have to write to standard out valid html. 1041 * 1042 * type RAW_TYPE is for when you have a content type that is not text/html or 1043 * text/plain. for example you want to write to standard out JSON or XML data. 1044 * you will have to set valid mime type for what you are sending back to the browser. 1045 * 1046 * types TEXT_PLAIN and TEXT_PLAIN_NO_CACHE is a lazy short cut if you just want to return 1047 * to the browser anything that has the mime content-type of text/plain. 1048 */ 1049 void pageStart() { 1050 if (mime_type_set) { 1051 this.pageStart(mime_type); 1052 } else { 1053 this.pageStart(MIMETYPE.TEXT_PLAIN); 1054 } 1055 } 1056 1057 void pageStart(MIMETYPE type) { 1058 string content_type = ""; 1059 1060 switch (type) { 1061 case MIMETYPE.TEXT_HTML: 1062 case MIMETYPE.TEXT_HTML_NO_CACHE: 1063 case MIMETYPE.TEXT_HTML_RAW: 1064 case MIMETYPE.TEXT_HTML_NO_CACHE_RAW: 1065 content_type = "text/html"; 1066 break; 1067 case MIMETYPE.RAW_TYPE: 1068 content_type = custom_content_type; 1069 break; 1070 case MIMETYPE.TEXT_PLAIN: 1071 case MIMETYPE.TEXT_PLAIN_NO_CACHE: 1072 //Fall through to default 1073 default: 1074 content_type="text/plain"; 1075 break; 1076 } 1077 1078 1079 if( type == MIMETYPE.TEXT_HTML_NO_CACHE || 1080 type == MIMETYPE.TEXT_PLAIN_NO_CACHE || 1081 type == MIMETYPE.TEXT_HTML_NO_CACHE_RAW) { 1082 write("Expires: 0\r\n"); 1083 write("Cache-Control: no-store\r\n"); 1084 } 1085 1086 1087 if (content_type == "text/html") { 1088 if (cookie.data.length != 0) { 1089 writef("Set-Cookie: %s\r\n",cookie.toString()); 1090 } 1091 } 1092 1093 if (content_type.length != 0 ) { 1094 writef("Content-type: %s\r\n",content_type); 1095 write("\n"); 1096 if (type == MIMETYPE.TEXT_HTML_NO_CACHE || type == MIMETYPE.TEXT_HTML) { 1097 write("<html>"); 1098 write("<head>"); 1099 if (type == MIMETYPE.TEXT_HTML_NO_CACHE) { 1100 write("<meta http-equiv=\"CACHE-CONTROL\" " ~ 1101 "content=\"NO-CACHE\">\r\n"); 1102 } 1103 } 1104 } 1105 } 1106 1107 /** 1108 * stream the contents of a file back to the browser. you 1109 * will have to store the file in the cgi class instance by 1110 * setting a CGIVAL with CGIFILE class. 1111 */ 1112 void streamFile(string name) { 1113 this.streamFile(name,0); 1114 } 1115 1116 void streamFile(string name, int index) { 1117 int i = 0; 1118 int count = 0; 1119 1120 for (i = 0; i != cgiVals.length; ++i) { 1121 if (cgiVals[i] && 1122 cgiVals[i].name == name && 1123 cgiVals[i].type == CGI_VAR_TYPE.FILE) { 1124 if (count == index) { 1125 writef("Content-type: %s\r\n\n",cgiVals[i].fData.content_type); 1126 writef("%s",cgiVals[i].fData.content); 1127 break; 1128 } 1129 count++; 1130 } 1131 } 1132 } 1133 1134 /** 1135 * end the page being returned 1136 * 1137 * if you used one of the shortcut MIMETYPE's TEXT_HTML 1138 * and TEXT_HTML_NO_CACHE this will close the <html> tag 1139 * created for you. 1140 */ 1141 void pageEnd() { 1142 if (mime_type_set) { 1143 this.pageEnd(mime_type); 1144 } else { 1145 this.pageEnd(MIMETYPE.TEXT_HTML); 1146 } 1147 } 1148 1149 /** 1150 * 1151 */ 1152 void pageEnd(MIMETYPE type) { 1153 switch (type) { 1154 case MIMETYPE.TEXT_HTML: 1155 case MIMETYPE.TEXT_HTML_NO_CACHE: 1156 write("</html>\n"); 1157 break; 1158 default: 1159 break; 1160 } 1161 } 1162 1163 /** 1164 * set the MIMETYPE. 1165 */ 1166 void setMimeType(MIMETYPE type) { 1167 mime_type = type; 1168 mime_type_set=1; 1169 } 1170 1171 void setMimeType(string custom_content_type) { 1172 mime_type=MIMETYPE.RAW_TYPE; 1173 mime_type_set=1; 1174 this.custom_content_type = custom_content_type; 1175 } 1176 1177 /** 1178 * get the current MIMETYPE 1179 */ 1180 MIMETYPE getMimeType() { 1181 return mime_type; 1182 } 1183 1184 /** 1185 * set the class instance mime type to common mimetypes 1186 * based on a file extension. 1187 * 1188 * Note: Does not include every possible mime type, and 1189 * will be added to in future releases. It could also 1190 * be smarter by doing a look up, but I'm trying to be 1191 * light weight and not require a support file, or 1192 * having to make a external connection to get data. if 1193 * you need that kind of functionality, then it's up to 1194 * the application developer to make that design call. 1195 * 1196 */ 1197 void discoverMimeType(string extension) { 1198 // figure out our content type based off the extension. 1199 switch (extension) { 1200 case "widget": 1201 case "WIDGET": 1202 case "html": 1203 case "HTML": 1204 this.setMimeType(MIMETYPE.TEXT_HTML_RAW); 1205 break; 1206 case "txt": 1207 case "TEXT": 1208 case "text/plain": 1209 this.setMimeType(MIMETYPE.TEXT_PLAIN); 1210 break; 1211 case "xml": 1212 case "XML": 1213 this.setMimeType("application/xml"); 1214 break; 1215 case "js": 1216 case "JS": 1217 this.setMimeType("application/javascript"); 1218 break; 1219 case "css": 1220 case "CSS": 1221 this.setMimeType("text/css"); 1222 break; 1223 case "png": 1224 case "PNG": 1225 this.setMimeType("image/png"); 1226 break; 1227 case "jpg": 1228 case "JPG": 1229 case "jpeg": 1230 case "JPEG": 1231 this.setMimeType("image/png"); 1232 break; 1233 case "svg": 1234 case "SVG": 1235 this.setMimeType("image/svg+xml"); 1236 break; 1237 case "gif": 1238 case "GIF": 1239 this.setMimeType("image/gif"); 1240 break; 1241 case "tif": 1242 case "TIF": 1243 case "tiff": 1244 case "TIFF": 1245 this.setMimeType("image/tiff"); 1246 break; 1247 default: 1248 this.setMimeType(MIMETYPE.TEXT_PLAIN); 1249 break; 1250 } 1251 } 1252 1253 /** 1254 * set the cookie. 1255 */ 1256 void setCookie(COOKIE cookie) { 1257 this.cookie = cookie; 1258 } 1259 1260 /** 1261 * Get the cookie. 1262 */ 1263 COOKIE getCookie() { 1264 return this.cookie; 1265 } 1266 1267 /** 1268 * lazy javascript that you can use to tell the browser 1269 * to keep scrolling down as you write content. This is 1270 * handy if you said don't cache, and your web server 1271 * is set up to not compress the returned data to browser. 1272 * 1273 * for example you want to write a UI that you upload a .csv 1274 * file to, and you have to do things with the data, and you 1275 * have implemented a loging system that writes to a file and 1276 * stdout at the same time, you could display your logs real 1277 * time back to a browser as you process the .csv file. 1278 * 1279 * it's just been useful. use it or don't. 1280 */ 1281 void autoScrollInit() { 1282 write("<script type=\"text/javascript\">\n"); 1283 write("function AutoScroll()\n"); 1284 write("{\n"); 1285 write(" var cw = document.body.clientHeight;\n"); 1286 write(" var fh = document.body.scrollHeight;\n"); 1287 write(" if (fh > cw) {\n"); 1288 write(" document.body.scrollTop = fh;\n"); 1289 write(" }\n"); 1290 write("}\n"); 1291 write("</script>\n"); 1292 } 1293 1294 /** 1295 * tell the browser to run the AutoScroll javascript. 1296 */ 1297 void runAutoScroll() { 1298 write("<script type=\"text/javascript\">\n"); 1299 write("AutoScroll();"); 1300 write("</script>\n"); 1301 } 1302 1303 /** 1304 * getter method for error_msg. 1305 */ 1306 string getErrorMsg() { 1307 return error_msg; 1308 } 1309 }